package cn.iocoder.yudao.module.im.service.message;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.convert.message.MessageConvert;
import cn.iocoder.yudao.framework.websocket.core.enums.WebSocketMessageTypeEnum;
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessagePageReqVO;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessageReadReqVO;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessageRespVO;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessageSendReqVO;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessageSendRespVO;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessageUndoReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.MessageDO;
import cn.iocoder.yudao.module.im.dal.mysql.message.MessageMapper;
import cn.iocoder.yudao.module.im.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.im.enums.MessageTypeEnum;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.im.controller.app.message.vo.AppMessageUserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;

/**
 * 消息 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Slf4j
public class MessageServiceImpl implements MessageService {

    @Resource
    private MessageMapper messageMapper;
    
    @Resource
    private MemberUserApi memberUserApi;
    
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    
    @Resource
    private WebSocketMessageSender webSocketMessageSender;

    /**
     * 消息频率限制缓存key前缀
     */
    private static final String SEND_INTERVAL_KEY = "send_";
    
    /**
     * 默认发送间隔（秒）
     */
    private static final int DEFAULT_SEND_INTERVAL = 1;
    
    /**
     * 文本消息最大长度
     */
    private static final int MAX_TEXT_LENGTH = 2048;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppMessageSendRespVO sendMessage(Long userId, AppMessageSendReqVO sendReqVO) {
        // 1. 参数校验
        validateSendMessage(userId, sendReqVO);
        
        // 2. 频率限制检查
        checkSendInterval(userId);
        
        // 3. 内容检查
        validateMessageContent(sendReqVO);
        
        // 4. 权限检查
        validateSendPermission(userId, sendReqVO);
        
        // 5. 构建消息数据
        MessageDO messageDO = buildMessageDO(userId, sendReqVO);
        
        // 6. 保存消息
        Long messageId = createMessage(messageDO);
        messageDO.setId(messageId);
        
        // 7. 发送WebSocket推送
        sendWebSocketMessage(messageDO, sendReqVO);
        
        // 8. 设置发送间隔缓存
        setSendInterval(userId);
        
        // 9. 构建响应数据
        return buildSendResponse(messageDO, sendReqVO);
    }

    @Override
    public Long createMessage(MessageDO messageDO) {
        // 更新之前消息的is_last状态
        messageMapper.updateIsLastByChatIdentify(messageDO.getChatIdentify());
        
        // 插入新消息
        messageMapper.insert(messageDO);
        return messageDO.getId();
    }

    @Override
    public MessageDO getMessage(Long id) {
        return messageMapper.selectById(id);
    }

    /**
     * 校验发送消息参数
     */
    private void validateSendMessage(Long userId, AppMessageSendReqVO sendReqVO) {
        if (userId == null) {
            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
        }
        
        if (sendReqVO.getToContactId() == null) {
            throw ServiceExceptionUtil.exception(CONTACT_NOT_EXISTS);
        }
        
        if (StrUtil.isBlank(sendReqVO.getType())) {
            throw ServiceExceptionUtil.exception(MESSAGE_TYPE_INVALID);
        }
        
        if (StrUtil.isBlank(sendReqVO.getContent())) {
            throw ServiceExceptionUtil.exception(MESSAGE_CONTENT_EMPTY);
        }
    }

    /**
     * 检查发送频率限制
     */
    private void checkSendInterval(Long userId) {
        String cacheKey = SEND_INTERVAL_KEY + userId;
        if (Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey))) {
            throw ServiceExceptionUtil.exception(SEND_FREQUENCY_LIMIT, DEFAULT_SEND_INTERVAL);
        }
    }

    /**
     * 验证消息内容
     */
    private void validateMessageContent(AppMessageSendReqVO sendReqVO) {
        if (MessageTypeEnum.TEXT.getType().equals(sendReqVO.getType())) {
            // 限制文字内容长度
            String text = StrUtil.cleanBlank(sendReqVO.getContent());
            if (text.length() > MAX_TEXT_LENGTH) {
                throw ServiceExceptionUtil.exception(MESSAGE_CONTENT_TOO_LONG, text.length());
            }
        }
    }

    /**
     * 验证发送权限
     */
    private void validateSendPermission(Long userId, AppMessageSendReqVO sendReqVO) {
        // 这里可以添加具体的权限检查逻辑
        // 例如：检查是否为好友关系、群组成员等
        
        // 检查用户是否存在
        MemberUserRespDTO user = memberUserApi.getUser(userId);
        if (user == null) {
            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
        }
        
        // 如果是群聊，检查群组权限
        if (sendReqVO.getIsGroup() != null && sendReqVO.getIsGroup() == 1) {
            // TODO: 添加群组权限检查
        } else {
            // 如果是单聊，检查接收者是否存在
            MemberUserRespDTO toUser = memberUserApi.getUser(sendReqVO.getToContactId());
            if (toUser == null) {
                throw ServiceExceptionUtil.exception(CONTACT_NOT_EXISTS);
            }
        }
    }

    /**
     * 构建消息DO对象
     */
    private MessageDO buildMessageDO(Long userId, AppMessageSendReqVO sendReqVO) {
        // 生成聊天标识
        String chatIdentify = generateChatIdentify(userId, sendReqVO.getToContactId(), sendReqVO.getIsGroup());
        
        // 处理@用户列表
        String atStr = null;
        if (sendReqVO.getAt() != null && !sendReqVO.getAt().isEmpty()) {
            atStr = StrUtil.join(",", sendReqVO.getAt());
        }
        
        return MessageDO.builder()
                .msgId(sendReqVO.getId())
                .fromUserId(userId)
                .toUserId(sendReqVO.getToContactId())
                .chatIdentify(chatIdentify)
                .type(sendReqVO.getType())
                .content(sendReqVO.getContent())
                .isGroup(sendReqVO.getIsGroup() != null ? sendReqVO.getIsGroup() : 0)
                .isRead(sendReqVO.getIsGroup() != null && sendReqVO.getIsGroup() == 1 ? 1 : 0)
                .isLast(1) // 新消息设为最新
                .fileId(sendReqVO.getFileId())
                .fileCate(sendReqVO.getFileCate())
                .fileSize(sendReqVO.getFileSize())
                .fileName(sendReqVO.getFileName())
                .at(atStr)
                .pid(sendReqVO.getPid())
                .extra(sendReqVO.getExtra())
                .status(1) // 默认启用状态
                .sendTime(sendReqVO.getSendTime() != null ? sendReqVO.getSendTime() : System.currentTimeMillis())
                .build();
    }

    /**
     * 生成聊天标识
     */
    private String generateChatIdentify(Long fromUserId, Long toUserId, Integer isGroup) {
        if (isGroup != null && isGroup == 1) {
            // 群聊标识：group-{groupId}
            return "group-" + toUserId;
        } else {
            // 单聊标识：小的用户ID在前
            long minId = Math.min(fromUserId, toUserId);
            long maxId = Math.max(fromUserId, toUserId);
            return minId + "-" + maxId;
        }
    }

    /**
     * 发送WebSocket消息推送
     */
    private void sendWebSocketMessage(MessageDO messageDO, AppMessageSendReqVO sendReqVO) {
        try {
            // 构建WebSocket消息数据
            Map<String, Object> messageData = new HashMap<>();
            messageData.put("msgId", messageDO.getId());
            messageData.put("id", messageDO.getMsgId());
            messageData.put("fromUserId", messageDO.getFromUserId());
            messageData.put("toUserId", messageDO.getToUserId());
            messageData.put("type", messageDO.getType());
            messageData.put("content", messageDO.getContent());
            messageData.put("isGroup", messageDO.getIsGroup());
            messageData.put("status", "succeed");
            messageData.put("sendTime", messageDO.getSendTime());
            
            if (sendReqVO.getAt() != null) {
                messageData.put("at", sendReqVO.getAt());
            }
            
            // 确定WebSocket消息类型
            String wsMessageType = messageDO.getIsGroup() == 1 ? 
                    WebSocketMessageTypeEnum.GROUP.getType() : 
                    WebSocketMessageTypeEnum.SIMPLE.getType();
            
            // 发送给接收者
            webSocketMessageSender.sendObject(UserTypeEnum.MEMBER.getValue(), messageDO.getToUserId(), wsMessageType, messageData);
            
            // 如果是单聊，也发送给发送者（用于多端同步）
            if (messageDO.getIsGroup() == 0) {
                webSocketMessageSender.sendObject(UserTypeEnum.MEMBER.getValue(), messageDO.getFromUserId(), wsMessageType, messageData);
            }
            
        } catch (Exception e) {
            log.error("[sendWebSocketMessage][消息推送失败] messageId={}", messageDO.getId(), e);
        }
    }

    /**
     * 设置发送间隔缓存
     */
    private void setSendInterval(Long userId) {
        String cacheKey = SEND_INTERVAL_KEY + userId;
        redisTemplate.opsForValue().set(cacheKey, System.currentTimeMillis(), DEFAULT_SEND_INTERVAL, TimeUnit.SECONDS);
    }

    /**
     * 构建发送响应
     */
    private AppMessageSendRespVO buildSendResponse(MessageDO messageDO, AppMessageSendReqVO sendReqVO) {
        AppMessageSendRespVO response = MessageConvert.INSTANCE.convert(messageDO);
        response.setStatus("succeed");
        if (sendReqVO.getAt() != null) {
            response.setAt(sendReqVO.getAt());
        }
        return response;
    }

    @Override
    public PageResult<AppMessageRespVO> getMessagePage(Long userId, AppMessagePageReqVO pageReqVO) {
        // 1. 参数校验
        if (userId == null) {
            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
        }
        if (pageReqVO.getToContactId() == null) {
            throw ServiceExceptionUtil.exception(CONTACT_NOT_EXISTS);
        }

        // 2. 分页查询消息
        PageResult<MessageDO> pageResult = messageMapper.selectMessagePage(pageReqVO, userId);
        
        // 3. 转换为响应VO
        List<AppMessageRespVO> voList = MessageConvert.INSTANCE.convertToAppMessageRespVOList(pageResult.getList());
        
        // 4. 填充用户信息
        fillUserInfo(voList);
        
        return new PageResult<>(voList, pageResult.getTotal());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void readMessage(Long userId, AppMessageReadReqVO readReqVO) {
        // 1. 参数校验
        if (userId == null) {
            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
        }
        if (readReqVO.getToContactId() == null) {
            throw ServiceExceptionUtil.exception(CONTACT_NOT_EXISTS);
        }

        // 2. 更新消息已读状态
        if (readReqVO.getMessageIds() != null && !readReqVO.getMessageIds().isEmpty()) {
            // 批量更新指定消息为已读
            messageMapper.updateMessageReadStatus(readReqVO.getMessageIds(), 1);
        } else if (readReqVO.getFromUserId() != null) {
            // 根据聊天标识和发送者更新消息为已读
            String chatIdentify = messageMapper.generateChatIdentify(userId, readReqVO.getToContactId(), readReqVO.getIsGroup());
            messageMapper.updateMessageReadStatusByChatIdentifyAndFromUser(chatIdentify, readReqVO.getFromUserId(), 1);
        }

        // 3. 发送WebSocket通知已读
        try {
            Map<String, Object> messageData = new HashMap<>();
            messageData.put("toContactId", readReqVO.getToContactId());
            messageData.put("fromUserId", userId);
            messageData.put("isGroup", readReqVO.getIsGroup());
            if (readReqVO.getMessageIds() != null) {
                messageData.put("messageIds", readReqVO.getMessageIds());
            }
            
            // 发送已读通知
            webSocketMessageSender.sendObject(UserTypeEnum.MEMBER.getValue(), readReqVO.getToContactId(), 
                    WebSocketMessageTypeEnum.IS_READ.getType(), messageData);
                    
        } catch (Exception e) {
            log.error("[readMessage][已读通知发送失败] userId={}, toContactId={}", userId, readReqVO.getToContactId(), e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppMessageRespVO undoMessage(Long userId, AppMessageUndoReqVO undoReqVO) {
        // 1. 参数校验
        if (userId == null) {
            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
        }
        if (undoReqVO.getId() == null) {
            throw ServiceExceptionUtil.exception(MESSAGE_NOT_EXISTS);
        }

        // 2. 查询消息
        MessageDO messageDO = messageMapper.selectById(undoReqVO.getId());
        if (messageDO == null) {
            throw ServiceExceptionUtil.exception(MESSAGE_NOT_EXISTS);
        }

        // 3. 校验撤回权限（只能撤回自己的消息，且在2分钟内）
        if (!messageDO.getFromUserId().equals(userId)) {
            throw ServiceExceptionUtil.exception(MESSAGE_UNDO_PERMISSION_DENIED);
        }
        
        // 检查是否在撤回时间限制内（2分钟 = 120秒）
        long currentTime = System.currentTimeMillis();
        long messageTime = messageDO.getSendTime();
        if (currentTime - messageTime > 120000) {
            throw ServiceExceptionUtil.exception(MESSAGE_UNDO_TIME_EXPIRED);
        }

        // 4. 更新消息类型为事件消息
        messageDO.setType("event");
        messageDO.setContent("撤回了一条消息");
        messageDO.setUpdateTime(LocalDateTime.now());
        messageMapper.updateById(messageDO);

        // 5. 发送WebSocket撤回通知
        try {
            Map<String, Object> messageData = new HashMap<>();
            messageData.put("id", messageDO.getId());
            messageData.put("msgId", messageDO.getMsgId());
            messageData.put("fromUserId", messageDO.getFromUserId());
            messageData.put("toContactId", messageDO.getToUserId());
            messageData.put("content", "撤回了一条消息");
            messageData.put("sendTime", messageDO.getSendTime());
            messageData.put("isGroup", messageDO.getIsGroup());
            
            String wsMessageType = messageDO.getIsGroup() == 1 ? 
                    WebSocketMessageTypeEnum.GROUP.getType() : 
                    WebSocketMessageTypeEnum.SIMPLE.getType();
            
            // 发送给接收者
            webSocketMessageSender.sendObject(UserTypeEnum.MEMBER.getValue(), messageDO.getToUserId(), 
                    WebSocketMessageTypeEnum.UNDO_MESSAGE.getType(), messageData);
            
            // 如果是单聊，也发送给发送者（用于多端同步）
            if (messageDO.getIsGroup() == 0) {
                webSocketMessageSender.sendObject(UserTypeEnum.MEMBER.getValue(), messageDO.getFromUserId(), 
                        WebSocketMessageTypeEnum.UNDO_MESSAGE.getType(), messageData);
            }
            
        } catch (Exception e) {
            log.error("[undoMessage][撤回通知发送失败] messageId={}", messageDO.getId(), e);
        }

        // 6. 返回撤回后的消息
        AppMessageRespVO response = MessageConvert.INSTANCE.convertToAppMessageRespVO(messageDO);
        fillUserInfo(List.of(response));
        return response;
    }

    /**
     * 填充用户信息
     */
    private void fillUserInfo(List<AppMessageRespVO> messageList) {
        if (messageList == null || messageList.isEmpty()) {
            return;
        }

        // 收集用户ID
        List<Long> userIds = messageList.stream()
                .flatMap(msg -> List.of(msg.getFromUserId(), msg.getToUserId()).stream())
                .distinct()
                .toList();

        // 批量查询用户信息
        List<MemberUserRespDTO> users = memberUserApi.getUserList(userIds);
        Map<Long, MemberUserRespDTO> userMap = users.stream()
                .collect(Collectors.toMap(MemberUserRespDTO::getId, Function.identity()));

        // 填充用户信息到消息中
        for (AppMessageRespVO message : messageList) {
            MemberUserRespDTO fromUser = userMap.get(message.getFromUserId());
            if (fromUser != null) {
                message.setFromUser(convertToAppMessageUserVO(fromUser));
            }
            
            MemberUserRespDTO toUser = userMap.get(message.getToUserId());
            if (toUser != null) {
                message.setToUser(convertToAppMessageUserVO(toUser));
            }
        }
    }

    /**
     * 转换用户信息
     */
    private AppMessageUserVO convertToAppMessageUserVO(MemberUserRespDTO userDTO) {
        AppMessageUserVO userVO = new AppMessageUserVO();
        userVO.setId(userDTO.getId());
        userVO.setUsername(userDTO.getMobile()); // 会员没有username，使用手机号
        userVO.setRealname(userDTO.getNickname());
        userVO.setNickname(userDTO.getNickname());
        userVO.setDisplayName(StrUtil.isNotBlank(userDTO.getNickname()) ? userDTO.getNickname() : userDTO.getMobile());
        userVO.setAvatar(userDTO.getAvatar());
        // TODO: 设置在线状态，需要从Redis或其他地方获取
        userVO.setIsOnline(0);
        return userVO;
    }

}